﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.CompilerServices;

namespace DecisionFramework.Instrument
{
	/// <summary>
	/// Provides a mechansism to instrument how long a set of operations take.
	/// </summary>
	/// <remarks>  
	/// Author: Greg Cowin
	/// </remarks> 
	public class Instrumentation
	{
		/// <summary>
		/// Operation name to performance metrics mapping.
		/// </summary>
		private Dictionary<string, PerformanceMetrics> operationNameToPerformanceMap = new Dictionary<string, PerformanceMetrics>();
 
		/// <summary>
		/// The operation to measure.
		/// </summary>
		public delegate void Operation();

		/// <summary>
		/// The name that identifies this instrumentation; can be class name.
		/// </summary>
		public string Name { get; set; }

		/// <summary>
		/// Handles reporting of the information when the PublishPolicy decides 
		/// that it is time to report.
		/// </summary>
		public IReportProvider ReportInstrumentation { get; set; }

		/// <summary>
		/// The publish policy decides when to report the instrumentation.
		/// </summary>
		public IPublishPolicy PublishPolicy { get; set; }

		/// <summary>
		/// The last time that the instrumentation was reported.
		/// </summary>
		public DateTime LastReportTimeUtc { get; set; }

		/// <summary>
		/// Creates an Instrumentation with the given name.
		/// </summary>
		/// <param name="name">The name of the instrumentation.</param>
		public Instrumentation(string name)
		{
			Name = name;
			LastReportTimeUtc = DateTime.UtcNow;
		}

		public Instrumentation()
		{
			Name = "Unknown";
			LastReportTimeUtc = DateTime.UtcNow;
		}

		/// <summary>
		/// Reset the instrumentation.
		/// </summary>
        [MethodImpl(MethodImplOptions.Synchronized)]
		public void Reset()
		{
			operationNameToPerformanceMap = new Dictionary<string, PerformanceMetrics>();
			LastReportTimeUtc = DateTime.UtcNow;
		}

		/// <summary>
		/// Record the time in ticks for the given operation name.
		/// </summary>
		/// <param name="operationName">The name of the operation to measure.</param>
		/// <param name="timeInTicks">The time in ticks that the operation took.</param>
		private void Record(string operationName, long timeInTicks)
		{
			if (! operationNameToPerformanceMap.ContainsKey(operationName))
				operationNameToPerformanceMap[operationName] = new PerformanceMetrics {OperationName = operationName};
			operationNameToPerformanceMap[operationName].Record(timeInTicks);
		}

		/// <summary>
		/// Returns the metrics for the operation with the given name; null,
		/// if it does not exist.
		/// </summary>
		/// <param name="operationName">The metrics for this operation will be returned.</param>
		/// <returns>Returns the metrics for the operation with the given name; null,
		/// if it does not exist.
		/// </returns>
		public PerformanceMetrics MetricsForOperation(string operationName)
		{
			if (!operationNameToPerformanceMap.ContainsKey(operationName))
				return null;
			return operationNameToPerformanceMap[operationName];
		}

		/// <summary>
		/// Measure the given operation. The metrics will be identified using the
		/// given operation name.
		/// </summary>
		/// <param name="operationName">The name of the operation to measure.</param>
		/// <param name="operation">The operation to measure.</param>
		public void Measure(string operationName, Operation operation)  
		{
			long startTicks = DateTime.Now.Ticks;
			operation.Invoke();
			long endTicks = DateTime.Now.Ticks;
			Record(operationName, endTicks - startTicks);

			if (PublishPolicy != null && PublishPolicy.ShouldPublish(this) && ReportInstrumentation != null)
			{
				ReportInstrumentation.Report(this);
				Reset();
			}
		}

		public override string ToString()
		{
			StringBuilder builder = new StringBuilder();
			builder.Append("Instrumentation: ");
			builder.Append(Name);
            builder.Append(" Operations: ");
            builder.Append(operationNameToPerformanceMap.Count());
           
			foreach (string serviceName in operationNameToPerformanceMap.Keys)
			{
				builder.Append("\n\n");
				builder.Append(operationNameToPerformanceMap[serviceName].ToString());
			}

			return builder.ToString();
		}
	}
}
